﻿/*
 * Copyright © 2020, Wuxi Safover Intelligent Technology Co., Ltd. All rights reserved.
 * 
 * 无锡安永智能科技有限公司版权所有，未经许可，不得用于商业用途
 * 
 * Visit: https://www.safover.com/ 
 */

using log4net;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using Safover.LidarCollector.Core;

namespace Safover.LidarCollector.Managers
{
    /// <summary>
    /// 单次数据采集与缓存类
    /// </summary>
    internal class DataCacher : ILidarDataListenner
    {
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        private static readonly string Name = "DataCacher";

        public SingleCollectionWorkingStatusChangedEventHandler DataCollectorStatusChangedEvent { get; set; }

        /// <summary>
        /// 当全部通道的数据采集结束后，主动触发此信号量
        /// </summary>
        private Semaphore DataCacheFullSemaphore { get; }

        private bool isCathing = false;
        private bool IsCaching
        {
            get { return isCathing; }
            set
            {
                isCathing = value;

                if (value)
                {
                    CacheStartTime = DateTime.Now;

                    DeviceManager.DeviceFactoryInstance.AcquisitionCard.Subscribe(this);
                }
                else
                {
                    CacheEndTime = DateTime.Now;

                    DeviceManager.DeviceFactoryInstance.AcquisitionCard.Unsubscribe(this);
                }
            }
        }

        private bool ShouldContinue { get; set; }

        private readonly object locker = new object();

        private SortedDictionary<int, List<double>> ChannelCaches { get; }

        private bool IsCacheFull
        {
            get
            {
                lock (locker)
                {
                    foreach (var channel in ChannelCaches.Values)
                    {
                        if (channel.Count < 3000)
                        {
                            return false;
                        }
                    }

                    return true;
                }
            }
        }

        /// <summary>
        /// 本次 StartCache 采集开始时间
        /// </summary>
        public DateTime CacheStartTime { get; private set; }

        /// <summary>
        /// 本次采集结束时间
        /// </summary>
        public DateTime CacheEndTime { get; private set; }

        //public LidarWorkMode LidarWorkMode { get; private set; }

        internal DataCacher()
        {
            const int maxChannelNumber = 4;

            DataCacheFullSemaphore = new Semaphore(0, 1);

            ChannelCaches = new SortedDictionary<int, List<double>>();
            for (int channelNumber = 0; channelNumber < maxChannelNumber; channelNumber++)
            {
                ChannelCaches.Add(channelNumber, new List<double>());
            }
        }

        internal void StartCache(TimeSpan timeout)
        {
            //await Task.Run(() => StartRealCache(timeout, lidarWorkMode));
            var result = StartRealCache(timeout);

            switch (result)
            {
                case CacheResult.Success:
                case CacheResult.Canceled:
                    return;
                case CacheResult.Timeout:
                    {
                        TrySecondChance(timeout);
                    }
                    return;
                case CacheResult.InvalidOperation:
                default:
                    throw new DataCollectionException($"{Name}: Cache error.");
            }
        }

        private void TrySecondChance(TimeSpan timeout)
        {
            // TODO: reset card
            DeviceManager.DeviceFactoryInstance.AcquisitionCard.Reset();
            Log.DebugWriteLine($"{Name}: Caching failed the first time, AC card has been reset, try caching again.");

            var result = StartRealCache(timeout);

            switch (result)
            {
                case CacheResult.Success:
                case CacheResult.Canceled:
                    return;
                case CacheResult.Timeout:
                case CacheResult.InvalidOperation:
                default:
                    throw new DataCollectionException($"{Name}: Caching failed after reset AC card.");
            }
        }

        private CacheResult StartRealCache(TimeSpan timeout)
        {
            if (IsCaching)
            {
                throw new InvalidOperationException($"{Name}: already caching.");
            }

            ClearCache();

            IsCaching = true;
            ShouldContinue = true;

            var stopwatch = new Stopwatch();
            stopwatch.Start();

            bool succeed;

            try
            {
                do
                {
                    // 等待一秒钟，判断采集是否结束？
                    succeed = DataCacheFullSemaphore.WaitOne(TimeSpan.FromSeconds(1));

                    if (succeed)
                    {
                        ReportStatus(false, "Data Collected", .9f);
                        return CacheResult.Success;
                    }
                    else
                    {
                        var message = $"Waiting Data, Timeout={(int)(timeout.TotalSeconds - stopwatch.Elapsed.TotalSeconds)}s";
                        var percentageCompletion = (float)Math.Round(.25 + stopwatch.Elapsed.TotalSeconds / timeout.TotalSeconds * .75, 2);

                        ReportStatus(true, message, percentageCompletion);
                    }

                } 
                while (ShouldContinue && stopwatch.Elapsed < timeout);

                if (!ShouldContinue)
                {
                    return CacheResult.Canceled;
                }
                else
                {
                    Log.DebugWriteLine($"{Name}: caching data exceeded time limit, timeout=[{timeout.TotalSeconds}s].");
                    return CacheResult.Timeout;
                }
            }
            finally
            {
                IsCaching = false;
            }
        }

        private void ReportStatus(bool isCollecting, string message, float percentageCompletion)
        {
            var e = new SingleCollectionWorkingStatusChangedEventArgs
            {
                IsSingleCollecting = isCollecting,
                Message = message,
                PercentageCompletion = percentageCompletion,
            };

            Log.DebugWriteLine($"{Name}: ShouldContinue={ShouldContinue} - {e}.");

            DataCollectorStatusChangedEvent?.Invoke(e);
        }

        internal void StopCache()
        {
            ShouldContinue = false;
            Log.DebugWriteLine($"{Name}: --------------StopCache is called,  ShouldContinue={ShouldContinue}.");
        }

        internal SortedDictionary<int, double[]> GetCachedData()
        {
            return ConvertCache(ChannelCaches);
        }

        private SortedDictionary<int, double[]> ConvertCache(SortedDictionary<int, List<double>> channelCaches)
        {
            var dictionary = new SortedDictionary<int, double[]>();

            lock (locker)
            {
                foreach (var channel in channelCaches)
                {
                    dictionary.Add(channel.Key, channel.Value.ToArray());
                }
            }

            return dictionary;
        }

        private void ClearCache()
        {
            lock (locker)
            {
                foreach (var channel in ChannelCaches.Values)
                {
                    channel.Clear();
                }
            }
        }

        private void SetData(int channelNumber, double[] data)
        {
            if (!IsCaching)
            {
                Log.DebugWriteLine($"{Name}: no caching, channel number=[{channelNumber}], data length=[{data.Length}]");
                return;
            }

            Log.DebugWriteLine($"{Name}: cache data, channel number=[{channelNumber}], data length=[{data.Length}]");
            lock (locker)
            {
                if (ChannelCaches.ContainsKey(channelNumber))
                {

                    if (ChannelCaches[channelNumber].Count <= 0)
                    {
                        ChannelCaches[channelNumber].AddRange(data);
                    }
                    else
                    {
                        Log.DebugWriteLine($"Channel number=[{channelNumber}], data already collected=[{ChannelCaches[channelNumber].Count}], refused to add more.", LogLevel.Error);
                    }
                }
                else
                {
                    throw new IndexOutOfRangeException($"{Name}: unconfigured channel, number=[{channelNumber}].");
                }
            }

            if (IsCacheFull)
            {
                DataCacheFullSemaphore.Release();
                IsCaching = false;
            }
        }

        void ILidarDataListenner.NotifyLidarData(LidarDataCollectedEventArgs e)
        {
            SetData(e.ChannelNumber, e.Values);
        }
    }
}
